/**
 * Copyright (C) 2008 by The Regents of the University of California
 * Redistribution of this file is permitted under the terms of the GNU
 * Public License (GPL).
 *
 * @author Junghoo "John" Cho <cho AT cs.ucla.edu>
 * @date 3/24/2008
 */

#include <algorithm>
#include <cstdio>
#include <iostream>
#include <fstream>
#include <string>
#include "Bruinbase.h"
#include "SqlEngine.h"
#include "BTreeIndex.h"

using namespace std;

// external functions and variables for load file and sql command parsing 
extern FILE* sqlin;
int sqlparse(void);


RC SqlEngine::run(FILE* commandline)
{
  fprintf(stdout, "Bruinbase> ");

  // set the command line input and start parsing user input
  sqlin = commandline;
  sqlparse();  // sqlparse() is defined in SqlParser.tab.c generated from
  // SqlParser.y by bison (bison is GNU equivalent of yacc)

  return 0;
}

RC SqlEngine::select(int attr, const string& table, const vector<SelCond>& cond)
{
  RecordFile rf;   // RecordFile containing the table
  RecordId   rid;  // record cursor for table scanning

  RC     rc;
  int    key;     
  string value;
  int    count;
  int    diff;

  // open the table file
  if ((rc = rf.open(table + ".tbl", 'r')) < 0) {
    fprintf(stderr, "Error: table %s does not exist\n", table.c_str());
    return rc;
  }

  // open the index file
  BTreeIndex idx;
  IndexCursor ic;
  bool index = (idx.open(table + ".idx", 'r') == 0);
  vector<RecordId> v_rid;
  bool eq_set = false;
  int eq_val;
  int lower_bound = INT_MIN;
  int upper_bound = INT_MAX;
  
  // check for value compare
  bool val_search = false;
  for (unsigned i = 0; i < cond.size(); i++) {
    if (cond[i].attr == 2) {
      val_search = true;
      break;
    }
  }

  // check for only non equal is present
  bool only_ne = false;
  if (cond.size() == 1 && cond[0].comp == SelCond::NE)
    only_ne = true;

  // filter rid using index if available
  // and if we are filtering the results
  if (index && (!cond.empty() || attr == 1 || attr == 4) 
      && !val_search && !only_ne) {
    vector<int> v_key;
    for (unsigned i = 0; i < cond.size(); i++) {
      // comparison on key
      switch (cond[i].comp) {
        case SelCond::EQ:
          // can't have multiple equality for multiple values
          if (eq_set && eq_val != atoi(cond[i].value)) {
            v_rid.clear();
            break;
          }

          eq_set = true;
          eq_val = atoi(cond[i].value);

          if (idx.locate(eq_val, ic) == 0)
            if (idx.readForward(ic, key, rid) == 0 && key == eq_val) {
              v_rid.push_back(rid);
              v_key.push_back(key);
            }

          break;

        case SelCond::GT:
          lower_bound = max(lower_bound, atoi(cond[i].value) + 1);
          break;

        case SelCond::LT:
          upper_bound = min(upper_bound, atoi(cond[i].value));
          break;

        case SelCond::GE:
          lower_bound = max(lower_bound, atoi(cond[i].value));
          break;

        case SelCond::LE:
          upper_bound = min(upper_bound, atoi(cond[i].value) + 1);
          break;

        default:
          break;
      }
    }

    // if we have an equality and the key is out of bounds, 
    // we have no candidates
    if (eq_set) {
      if (eq_val < lower_bound || eq_val >= upper_bound) {
        v_rid.clear();
        v_key.clear();
      }
    }

    else {
      key = lower_bound;
      idx.locate(key, ic);
      while (idx.readForward(ic, key, rid) == 0 && key < upper_bound) {
        v_rid.push_back(rid);
        v_key.push_back(key);
      }
    }

    // print only keys
    if (attr == 1) {
      for (int i = 0; i < v_key.size(); i++)
        fprintf(stdout, "%d\n", v_key[i]);
      return 0;
    }

    // print matching tuple count if "select count(*)"
    if (attr == 4) {
      fprintf(stdout, "%d\n", v_key.size());
      return 0;
    }
  }

  else {
    for (rid.pid = rid.sid = 0; rid < rf.endRid(); rid++)
      v_rid.push_back(rid);
  }

  // scan the table file from the beginning
  count = 0;
  for (int i = 0; i < v_rid.size(); i++) {
    rid = v_rid[i];

    // read the tuple
    if ((rc = rf.read(rid, key, value)) < 0) {
      fprintf(stderr, "Error: while reading a tuple from table %s\n", table.c_str());
      goto exit_select;
    }

    // check the conditions on the tuple
    for (unsigned i = 0; i < cond.size(); i++) {
      // compute the difference between the tuple value and the condition value
      switch (cond[i].attr) {
        case 1:
          diff = key - atoi(cond[i].value);
          break;
        case 2:
          diff = strcmp(value.c_str(), cond[i].value);
          break;
      }

      // skip the tuple if any condition is not met
      switch (cond[i].comp) {
        case SelCond::EQ:
          if (diff != 0) goto next_tuple;
          break;
        case SelCond::NE:
          if (diff == 0) goto next_tuple;
          break;
        case SelCond::GT:
          if (diff <= 0) goto next_tuple;
          break;
        case SelCond::LT:
          if (diff >= 0) goto next_tuple;
          break;
        case SelCond::GE:
          if (diff < 0) goto next_tuple;
          break;
        case SelCond::LE:
          if (diff > 0) goto next_tuple;
          break;
      }
    }

    // the condition is met for the tuple. 
    // increase matching tuple counter
    count++;

    // print the tuple 
    switch (attr) {
      case 1:  // SELECT key
        fprintf(stdout, "%d\n", key);
        break;
      case 2:  // SELECT value
        fprintf(stdout, "%s\n", value.c_str());
        break;
      case 3:  // SELECT *
        fprintf(stdout, "%d '%s'\n", key, value.c_str());
        break;
    }

    // move to the next tuple
next_tuple:
    ;
  }
  
  // print matching tuple count if "select count(*)"
  if (attr == 4) {
    fprintf(stdout, "%d\n", count);
  }
  rc = 0;

  // close the table file and return
exit_select:
  rf.close();
  if (index)
    idx.close();
  return rc;
}

RC SqlEngine::load(const string& table, const string& loadfile, bool index)
{
  ifstream infile(loadfile.c_str());

  RecordFile rc;
  rc.open(table+".tbl", 'w');

  BTreeIndex idx;  
  if (index) { 
    idx.open(table+".idx", 'w');
  }

  string line;
  while (getline(infile, line)) {
    int key;
    string value;
    RecordId rid;

    SqlEngine::parseLoadLine(line, key, value);
    rc.append(key, value, rid);
    // check for error messages here? -- joseph
    if (index) {
      idx.insert(key, rid);	
    }
  }

  rc.close();
  infile.close();
  if (index) {
    idx.close();
  }

  return 0;
}

RC SqlEngine::parseLoadLine(const string& line, int& key, string& value)
{
  const char *s;
  char        c;
  string::size_type loc;

  // ignore beginning white spaces
  c = *(s = line.c_str());
  while (c == ' ' || c == '\t') { c = *++s; }

  // get the integer key value
  key = atoi(s);

  // look for comma
  s = strchr(s, ',');
  if (s == NULL) { return RC_INVALID_FILE_FORMAT; }

  // ignore white spaces
  do { c = *++s; } while (c == ' ' || c == '\t');

  // if there is nothing left, set the value to empty string
  if (c == 0) { 
    value.erase();
    return 0;
  }

  // is the value field delimited by ' or "?
  if (c == '\'' || c == '"') {
    s++;
  } else {
    c = '\n';
  }

  // get the value string
  value.assign(s);
  loc = value.find(c, 0);
  if (loc != string::npos) { value.erase(loc); }

  return 0;
}
